/*
 * Copyright (C) 2012, EADS France
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 */

package xowl.store.cache;

import java.util.HashMap;
import java.util.Map;
import xowl.store.rdf.RDFGraph;

/**
 *
 * @author Laurent WOUTERS
 */
public class StringStore {
    private static final int bufferSize = 2048;
    
    public static class Key implements RDFGraph.Key {
        private int offset;
        private int size;
        private Key next;
        private Key(int offset, int size) {
            this.offset = offset;
            this.size = size;
        }
        @Override public int hashCode() { return offset; }
        @Override public boolean equals(Object o) {
            if (!(o instanceof Key))
                return false;
            return (this == o);
        }
    }
    
    private java.io.RandomAccessFile file;
    private java.nio.charset.Charset charset;
    private int size;
    private Map<Integer, Key> index;
    private byte[] inner;
    
    public StringStore() {
        java.util.Random rand = new java.util.Random();
        String name = "store" + Integer.toString(rand.nextInt(Integer.MAX_VALUE));
        try { this.file = new java.io.RandomAccessFile(name, "rw"); }
        catch (java.io.IOException ex) { }
        this.charset = java.nio.charset.Charset.forName("UTF-8");
        this.index = new HashMap<Integer, Key>();
        this.inner = new byte[bufferSize];
    }
    
    public Key store(String value) {
        Key bucket = index.get(value.hashCode());
        if (bucket != null) {
            Key result = find(bucket, value);
            if (result != null)
                return result;
            result = dump(value);
            insert(bucket, result);
            return result;
        } else {
            bucket = dump(value);
            index.put(value.hashCode(), bucket);
            return bucket;
        }
    }
    
    private void insert(Key bucket, Key key) {
        while (bucket.next != null)
            bucket = bucket.next;
        bucket.next = key;
    }
    
    private Key find(Key bucket, String value) {
        byte[] bytes = value.getBytes(charset);
        try {
            while (bucket != null) {
                if (bucket.size == bytes.length) {
                    file.seek(bucket.offset);
                    read(bucket.size);
                    if (matches(bytes, inner))
                        return bucket;
                }
                bucket = bucket.next;
            }
            return null;
        } catch (java.io.IOException ex) {
            return null;
        }
    }
    
    private boolean matches(byte[] left, byte[] right) {
        for (int i=0; i!=left.length; i++)
            if (left[i] != right[i])
                return false;
        return true;
    }
    
    private Key dump(String value) {
        try {
            byte[] bytes = value.getBytes(charset);
            Key key = new Key(size, bytes.length);
            file.seek(size);
            file.write(bytes);
            size += bytes.length;
            return key;
        } catch (java.io.IOException ex) {
            return null;
        }
    }
    
    public String retrieve(Key key) {
        try {
            file.seek(key.size);
            read(key.size);
            return new String(inner, 0, key.size, charset);
        } catch (java.io.IOException ex) {
            return null;
        }
    }
    
    private void move(java.io.FileInputStream input, int offset) throws java.io.IOException {
        long remaining = offset;
        while (remaining > 0)
            remaining -= input.skip(remaining);
    }
    
    private void read(int length) throws java.io.IOException {
        int toRead = length;
        while (toRead > 0) {
            if (toRead > length || toRead > 2048) {
                System.out.println("fuck");
            }
            toRead -= file.read(inner, length-toRead, toRead);
        }
    }
}
